﻿using System;
using System.Diagnostics.Contracts;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;

namespace MathService.Reflection
{
    public class AnimatedProperty<TObject, TValue> : Property<TObject, TValue>
    {
        private readonly int f_Samples;
        private readonly int f_Timeout;
        private readonly Func<int, int, TValue> f_Translator;

        private bool f_Enabled;
        private Thread f_Thread;
        private ThreadPriority f_Priority = ThreadPriority.Normal;

        public bool Enable { get { return f_Enabled; } set { if(value) Start(); else Stop(); } }

        public ThreadPriority Priority
        {
            get { return f_Priority; }
            [MethodImpl(MethodImplOptions.Synchronized)]
            set
            {
                f_Priority = value;
                if(f_Enabled) f_Thread.Priority = value;
            }
        }

        public AnimatedProperty(TObject o, string Name,
            int Samples, int Timeout, Func<int, int, TValue> Translator,
            bool Private = false)
            : base(o, Name, Private)
        {
            Contract.Requires(Samples > 0);
            Contract.Requires(Timeout >= 0);
            Contract.Requires(Translator != null);

            f_Samples = Samples;
            f_Timeout = Timeout;
            f_Translator = Translator;
        }

        public void Start()
        {
            if(f_Enabled) return;
            lock(this)
            {
                if(f_Enabled) return;
                f_Thread = new Thread(Do) { Priority = f_Priority };
                f_Enabled = true;
                f_Thread.Start();
            }
        }

        public void Stop()
        {
            if(!f_Enabled) return;
            lock(this)
            {
                if(!f_Enabled) return;
                f_Enabled = false;
                f_Thread.Abort();
                if(!f_Thread.Join(2 * f_Timeout) && f_Thread.IsAlive)
                    f_Thread.Interrupt();
                f_Thread = null;
            }
        }

        private void Do()
        {
            var count = f_Samples;
            var timeout = f_Timeout;
            for(var i = 0; f_Enabled && i < count; i++)
            {
                Value = f_Translator(i, count);
                Thread.Sleep(timeout);
            }
        }

        [ContractInvariantMethod]
        private void InvariantCheck()
        {
            Contract.Invariant(f_Samples > 0);
            Contract.Invariant(f_Timeout >= 0);
        }
    }
}